package com.digital.welcome.websocket.mywebsocket.model;

import java.util.HashMap;
import java.util.List;

import com.digital.welcome.util.TemplateUtil;
import org.bouncycastle.util.Arrays;

import com.digital.welcome.websocket.mywebsocket.util.BitConverter;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Data
public final class WebSocketMessage {
    public static TemplateUtil tu = new TemplateUtil();
    public static byte IS_EFFECTIVE_CODE = (byte) 200;
    public static int HEADER_LENGTH = 10;

    public static int UINT32_LENGTH = 4;
    public static int UINT8_LENGTH = 1;

    public static int IS_EFFECTIVE_LOCATION = 0;
    public static int MESSAGE_EVENT_CODE_LOCATION = 1;

    public static int MESSAGE_TYPE_LOCATION = 5;
    public static int LENGTH_LOCATION = 6;
    public static int MESSAGE_LOCATION = 10;

    /// <summary>
    /// 该消息所响应的事件
    /// </summary>
    /// <value></value>
    private WebSocketMessageEvent MessageEvent;

    /// <summary>
    /// 该消息种类 (Text, Binary, Close)
    /// </summary>
    /// <value></value>
    private WebSocketMessageType MessageType;

    /// <summary>
    /// 该消息长度 (统一设置为 byte[] 的长度)
    /// </summary>
    /// <value></value>
    private int Length;


    private Object Message;

    public void setMessage(Object message) throws Exception {
        Message = message;
        if(message == null) return;
        if((MessageType == WebSocketMessageType.Text && message.getClass() != String.class) || 
        MessageType == WebSocketMessageType.Binary && message.getClass() != byte[].class)
        {
            String exceptionInfo =tu.setTemplate( "WebSocketMessageError : The Required MessageType Is '${}' But Offered Unmatched Type '${}'").formatted(MessageType, message.getClass());
            throw new Exception(exceptionInfo);
        }
            
        if(MessageType == WebSocketMessageType.Binary) Length = ((byte[])message).length;
        else Length = ((String)message).getBytes().length;
    }



    public WebSocketMessage() {
    }

    public WebSocketMessage(WebSocketMessageEvent messageEvent, WebSocketMessageType messageType, Object message) throws Exception
	{
		MessageEvent = messageEvent;
		MessageType = messageType;
		setMessage(message);
	}

    public WebSocketMessage(WebSocketMessageEvent MessageEvent, WebSocketMessageType MessageType, int Length,
            Object message) throws Exception {
        this.MessageEvent = MessageEvent;
        this.MessageType = MessageType;
        this.Length = Length;
        setMessage(message);
    }

    public WebSocketMessage MessageWithoutContent() throws Exception
	{
		return new WebSocketMessage(this.MessageEvent, this.MessageType, this.Length, null);
	}

    /// <summary>
    /// 由`WebSocketMessage` 对象转换为 字节数组.
    /// {
    /// 基于websocket协议的自定义协议 :
    ///
    /// byte 0 <==> IsEffective ? The Effective Code Is `200`
    /// byte 1 ~ 4 <==> WebSocketMessage.MessageEvent.Code (int32)
    /// byte 5 <==> WebSocketMessage.MessageType (enum = 3)
    /// byte 6 ~ 9 <==> WebSocketMessage.Length (int32)
    /// byte 10 ~ 10 + length <==> WebSocketMessage.Message
    /// }
    /// </summary>
    /// <param name="bytes"></param>
    /// <returns></returns>
    public byte[] setBytes() throws Exception
		{
			// Byte[] bytes = new Byte[Length + HEADER_LENGTH];// 消息长度 + 10 字节报文头部 + 1 字节结尾
			// bytes[IS_EFFECTIVE_LOCATION] = ((byte)IS_EFFECTIVE_CODE);// 设置为有效
            byte[] isEffectiveCodeSegment = new byte[1];
            isEffectiveCodeSegment[0] = (byte)IS_EFFECTIVE_CODE;
            
            HashMap<WebSocketMessageType, Byte> messageTypeHash = new HashMap<WebSocketMessageType, Byte>();
            messageTypeHash.put(WebSocketMessageType.Text, (byte)0);
            messageTypeHash.put(WebSocketMessageType.Binary, (byte)1);
            messageTypeHash.put(WebSocketMessageType.Close, (byte)2);
			// bytes[MESSAGE_TYPE_LOCATION] = messageTypeHash.get(MessageType);// 设置消息类型
            byte[] messageTypeSegment = new byte[1];
            messageTypeSegment[0] = messageTypeHash.get(MessageType);
            
            // byte[] eventCodeSegment = Arrays.copyOfRange(ArrayUtils.toPrimitive(bytes), MESSAGE_EVENT_CODE_LOCATION, UINT32_LENGTH);
			// byte[] lengthSegment = Arrays.copyOfRange(ArrayUtils.toPrimitive(bytes), LENGTH_LOCATION, UINT32_LENGTH);
            // byte[] messageSegment = Arrays.copyOfRange(ArrayUtils.toPrimitive(bytes), MESSAGE_LOCATION, Length);
			

			byte[] eventCodeSegment = BitConverter.getBytes(MessageEvent.Code);
            
			// BitConverter.GetBytes(Length).CopyTo(lengthSegment);
            byte[] lengthSegment = BitConverter.getBytes(Length);
			//Message 类型可能是string 或 byte[] 这里需要进行辨别
            byte[] messageSegment;
			if(MessageType == WebSocketMessageType.Text)
				// (Message as string).ToByteArray().CopyTo(messageSegment);
                messageSegment = BitConverter.getBytes((String)Message);
			else if(MessageType == WebSocketMessageType.Binary)
				// (Message as byte[]).CopyTo(messageSegment);
                messageSegment = (byte[])Message;
			else throw new Exception(tu.setTemplate("The Message Type ${} Is Invalid To Cast To ByteArray").formatted(MessageType));
			return Arrays.concatenate(Arrays.concatenate(isEffectiveCodeSegment, eventCodeSegment, messageTypeSegment, lengthSegment), messageSegment);
		}

        public static byte[] setBytes(List<WebSocketMessage> wsMessages) throws Exception {
            byte[] buffer = new byte[0];

            try {
                if(wsMessages.size() == 0) return buffer;
                for(int i = 0;i < wsMessages.size() - 1; i++) {
                    buffer = Arrays.concatenate(buffer, wsMessages.get(i).setBytes());

                }
                buffer = Arrays.concatenate(buffer, wsMessages.get(wsMessages.size() - 1).setBytes());

                return buffer;

            }catch (Exception e) {
                throw new Exception(tu.setTemplate("Failed To Send The Message, Buffer Bytes :\n${}").formatted(wsMessages.toString()), e);
            }
        }

        public static int getAWebSocketMessage(byte[] bytes, List<WebSocketMessage> wsMessages, Integer startIndex) throws Exception {
            
            try {
                if(bytes == null) throw new Exception("The Input Reference Can Not Be Null");
                byte[] bytesFromStartSegment = Arrays.copyOfRange(bytes, startIndex, bytes.length);
                log.debug("bytesFromStartSegment = {}", bytesFromStartSegment);
                if(bytesFromStartSegment[0] != WebSocketMessage.IS_EFFECTIVE_CODE || 
                bytesFromStartSegment.length < WebSocketMessage.MESSAGE_LOCATION)
                throw new Exception("The Stream Of ByteArray Is Not A Effective Message");

                byte[] lengthSegment = Arrays.copyOfRange(bytesFromStartSegment, WebSocketMessage.LENGTH_LOCATION, WebSocketMessage.LENGTH_LOCATION + WebSocketMessage.UINT32_LENGTH);
                int length = BitConverter.toInt(lengthSegment);

                byte[] bytesSegment = Arrays.copyOfRange(bytes, startIndex, startIndex + length + WebSocketMessage.HEADER_LENGTH);
                
                int eventCode = BitConverter.toInt(Arrays.copyOfRange(bytesSegment, WebSocketMessage.MESSAGE_EVENT_CODE_LOCATION, WebSocketMessage.MESSAGE_EVENT_CODE_LOCATION + UINT32_LENGTH));
                byte wsMessageTypeCode = bytesSegment[WebSocketMessage.MESSAGE_TYPE_LOCATION];
                WebSocketMessageType wsMessageType = wsMessageTypeCode == 0 ? WebSocketMessageType.Text
                    : wsMessageTypeCode == 1 ? WebSocketMessageType.Binary
                    : WebSocketMessageType.Close;

                Object message = Arrays.copyOfRange(bytesSegment, WebSocketMessage.MESSAGE_LOCATION, bytesSegment.length);
                if(wsMessageType == WebSocketMessageType.Text) message = new String((byte[])message);

                WebSocketMessage result = new WebSocketMessage(
                    new WebSocketMessageEvent(eventCode),
                    wsMessageType,
                    length,
                    message
                );

                bytes[startIndex] = 0;

                if(wsMessages != null) wsMessages.add(result);
                return length + WebSocketMessage.HEADER_LENGTH;

            
            }catch(Exception e) {
                throw new Exception(tu.setTemplate("Failed To Get WebSocketMessage From Bytes : \n {}").formatted(bytes.toString()), e);
            }
            
        }

        public static void getWebSocketMessages(byte[] bytes, List<WebSocketMessage> wsMessages, int startIndex) throws Exception {
            try {
                int byteOffset = startIndex;
                do {
                    byteOffset += getAWebSocketMessage(bytes, wsMessages, byteOffset);
                }while(byteOffset < bytes.length && bytes[byteOffset] == WebSocketMessage.IS_EFFECTIVE_CODE);
            }catch(Exception e) {
                throw new Exception("Failed To Get WebSocketMessages", e);
            }
        }

}
